#ifndef DSLKDJFLSKE_232480FJ0SDD9F8S09D_
#define DSLKDJFLSKE_232480FJ0SDD9F8S09D_

#include "pool.hpp"
#include "conv2d.hpp"
#include "fullnn.hpp"

#define LAYER_NONE		 0
#define LAYER_CONV2D	 1
#define LAYER_POOLING	 2
#define LAYER_FULL		 3

struct AlexLayer
{
	int 	Type;
	PiaConv Conv;
	PiaPool Pool;
	PiaFull Full;
	
	AlexLayer ()
	{
		Type = LAYER_NONE;
	}
};

class PiaAlexNet
{
public:
	bool Push(const AlexLayer &src);
	bool RunImgs(std::vector<MatrixXd> &input, VectorXd &dst);
	bool Run(PiaTensor& Data);
	void Print();
public:
	PiaAlexNet();
	virtual ~PiaAlexNet();
	
private:
	std::vector<AlexLayer> m_Layers;

};

PiaAlexNet::PiaAlexNet()
{
	m_Layers.clear();
}

PiaAlexNet::~PiaAlexNet()
{
	m_Layers.clear();
}

bool PiaAlexNet::Push(const AlexLayer & src)
{
	if (src.Type != LAYER_CONV2D  && 
		src.Type != LAYER_POOLING &&
		src.Type != LAYER_FULL) {
			cout << ">> error. layer type" << endl;
			return false;
		}
	m_Layers.push_back(src);
	return true;
}

bool PiaAlexNet::Run(PiaTensor& Data)
{
	for (int i = 0; i < (int)m_Layers.size(); i++)
	{
/*		cout<< ">> Layer :" 
			<< setw(3) << i << " ("
			<< setw(5) << Data.shape[0] 
			<< setw(5) << Data.shape[1] 
			<< setw(5) << Data.shape[2] 
			<< setw(5) << Data.shape[3] << ") "
			<< "data = (" 
			<< setw(5) << Data.data.rows() 
			<< setw(5) << Data.data.cols()
			<< ") ---------" 
			<< endl; */
/*		if (i == 4) {
			PiaConv2dData* p = (struct PiaConv2dData*)&Data;
			p->print();
		}*/
		switch (m_Layers[i].Type)
		{
			case LAYER_CONV2D: {
				if (false == m_Layers[i].Conv.Run(Data, Data)) {
					cout<< ">> error. PiaConv.Run() " << endl;
					return false;
				}
				break;
			}
			case LAYER_POOLING: {
				if (false == m_Layers[i].Pool.Run(Data, Data)) {
					cout<< ">> error. PiaPool.Run() " << endl;
					return false;
				}
				break;
			}
			case LAYER_FULL: {
				if (false == m_Layers[i].Full.Run(Data, Data)) {
					cout<< ">> error. PiaFull.Run() " << endl;
					return false;
				}
				break;
			}
		}
/*		cout<< "          " 
			<< setw(3) << i << " ("
			<< setw(5) << Data.shape[0] 
			<< setw(5) << Data.shape[1] 
			<< setw(5) << Data.shape[2] 
			<< setw(5) << Data.shape[3] << ") "
			<< "data = (" 
			<< setw(5) << Data.data.rows() 
			<< setw(5) << Data.data.cols()
			<< ") ---------" 
			<< endl;*/
	}
	
	return true;
}

/***********************************************
* src  For AlexNet, the input data usually is
*      image with several channels. 
*      As here is only a Forward CNN, we only
*      allow single sample one time. Just as batch = 1
*      in TensorFlow.
* dst  is a vector (dst.cols() = 1, dst.rows() = class number)
*      The length of output is the class number
*      that you need to classify.
*/
bool PiaAlexNet::RunImgs(std::vector<MatrixXd> &src, VectorXd &dst)
{	
	PiaTensor tmp; 
	tmp.FromImgs(src);
	Run(tmp);
	dst = tmp.data.middleCols(0, 1);
	return true;
}

void PiaAlexNet::Print()
{
	cout << "AlexCNN main parameters:" << endl;
	for (int i = 0; i < (int)m_Layers.size(); i++) {
		cout << "--------"
			 << "AlexCNN Layer " << i << " / " << (int)m_Layers.size() 
			 << "--------"<< endl;
		switch (m_Layers[i].Type)
		{
			case LAYER_CONV2D: {
				m_Layers[i].Conv.Print();
				break;
			}
			case LAYER_POOLING: {
				m_Layers[i].Pool.Print();
				break;
			}
			case LAYER_FULL: {
				m_Layers[i].Full.Print();
				break;
			}
		}
	}
	cout << "AlexCNN END" << endl;
}

#endif
